package com.ruicar.afs.cloud.channel.grade.utils;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * @class: SFTPChannel
 * @title: SFTP操作工具类
 * @desc: 默认打开sftp通道后，操作完成需要主动断开通道（出现异常系统自动断开）
 * @author: ningpeng
 * @date: 2017年7月10日 上午11:53:58
 * @since: 1.0.0
 */
@Slf4j
public class SFTPChannel {
    private static final Logger logger = LoggerFactory.getLogger(SFTPChannel.class);


    private final String CHANNEL_KEY = "sftp";
    private String ip;
    private String port;
    private String user;
    private String pwd;
    private String privateKeyPath;
    private String privateKeyPwd;
    private Session session = null;
    private ChannelSftp channel = null;
    private String sftpDir; // 服务器文件存放目录--下载银行文件目录
    private String downLoadFileSavePath;  // 默认下载存储目录
    private String sftpUploadDir;//服务器文件存放目录--上传丰田文件目录


    public String getDownLoadFileSavePath() {
        return downLoadFileSavePath;
    }

    public void setDownLoadFileSavePath(String downLoadFileSavePath) {
        this.downLoadFileSavePath = downLoadFileSavePath;
    }

    public String getSftpDir() {
        return sftpDir;
    }

    public void setSftpDir(String sftpDir) {
        this.sftpDir = sftpDir;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getPrivateKeyPath() {
        return privateKeyPath;
    }

    public void setPrivateKeyPath(String privateKeyPath) {
        this.privateKeyPath = privateKeyPath;
    }

    public String getPrivateKeyPwd() {
        return privateKeyPwd;
    }

    public void setPrivateKeyPwd(String privateKeyPwd) {
        this.privateKeyPwd = privateKeyPwd;
    }

    public String getSftpUploadDir() {
        return sftpUploadDir;
    }

    public void setSftpUploadDir(String sftpUploadDir) {
        this.sftpUploadDir = sftpUploadDir;
    }

    public SFTPChannel(String ip, String port, String user, String pwd, String privateKeyPath, String privateKeyPwd, String sftpDir, String sftpUploadDir) {
        this.ip = ip;
        this.port = port;
        this.user = user;
        this.pwd = pwd;
        this.privateKeyPath = privateKeyPath;
        this.privateKeyPwd = privateKeyPwd;
        this.sftpDir = sftpDir;
        this.sftpUploadDir = sftpUploadDir;
    }

    public void testSftp() throws Exception {
        logger.info("test sftp connect [" + ip + "]");
        try {
            this.initSftp();
        } catch (Exception e) {
            throw e;
        } finally {
            this.disconnect();
        }
    }

    /**
     * @throws Exception
     * @Description: sftp初始化
     * @author sijun.yu
     * @Created 2017年7月19日上午11:27:46
     */
    public void initSftp() throws Exception {
        if (StringUtils.isBlank(this.ip)) {
            throw new Exception("sftp IP地址不能为空!");
        }
        if (StringUtils.isBlank(this.user)) {
            throw new Exception("sftp 用户名不能为空!");
        }
        JSch jsch = new JSch();
        try {
            // 设置密钥和密码
            if (StringUtils.isNotBlank(this.privateKeyPath)) {// 设置不带口令的密钥
                if (StringUtils.isNotBlank(this.privateKeyPwd)) {// 设置带口令的密钥
                    jsch.addIdentity(privateKeyPath, privateKeyPwd);
                } else {
                    jsch.addIdentity(privateKeyPath);
                }
            }
            if (StringUtils.isNotBlank(port) && NumberUtils.isNumber(port)) {
                int pt = NumberUtils.toInt(port);
                session = jsch.getSession(user, ip, pt);
            } else {// 连接服务器，采用默认端口
                session = jsch.getSession(user, ip);
            }

            // 如果服务器连接不上，则抛出异常
            if (session == null) {
                throw new Exception("不能连接sftp服务器[" + ip + "]");
            }
            if (StringUtils.isNotBlank(this.pwd)) {
                // 设置登陆主机的密码
                session.setPassword(this.pwd);// 设置密码
            }
            // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
            session.setConfig("StrictHostKeyChecking", "no");
            // 设置登陆超时时间
            session.setTimeout(30000);
            logger.info("*******start connect sftp:[" + ip + "]...");
            // 打开连接
            session.connect();

            channel = (ChannelSftp) session.openChannel(CHANNEL_KEY);
            channel.connect();
            logger.info("********connect sftp:[" + ip + "] success");
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception("初始化sftp连接错误:" + e.getMessage());
        }
    }

    public void disconnect() {
        logger.info("********disconnect sftp:[" + ip + "] ");
        if (channel != null) {
            channel.disconnect();
            channel = null;
        }
        if (session != null) {
            session.disconnect();
            session = null;
        }
    }

    /**
     * @param dirNm
     * @throws Exception
     * @Description: 进入指定目录
     */
    public String cd(String remoteDir) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        try {
            String dir = this.getSftpSaveDir(null);
            channel.cd(dir);
            if (StringUtils.isNotBlank(remoteDir)) {
                channel.cd(remoteDir);
            }
            return channel.pwd();
        } catch (Exception ex) {
            logger.warn(ex.getMessage(), ex);
            this.disconnect();
            throw new Exception(ex.getMessage());
        }
    }

    /**
     * @param remoteDir
     * @return
     * @throws Exception
     * @Description: 查看路径下文件
     */
    public Vector<LsEntry> ls(String remoteDir) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        try {
            Vector<LsEntry> vector = channel.ls(remoteDir);
            return vector;
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            this.disconnect();
            throw new Exception(ex.getMessage());
        }
    }

    /**
     * @param dirs
     * @throws Exception
     * @Description: 创建目录, 支持多级以反斜杠/分割
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void mkdirs(String dirs) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        try {
            channel.cd("/");
            String[] split = StringUtils.split(dirs, "/");
            for (String string : split) {
                if (StringUtils.isNotBlank(string)) {
                    try {
                        channel.cd(string);
                    } catch (Exception e) {
                        channel.mkdir(string);
                        channel.cd(string);
                    }
                }
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            this.disconnect();
            throw new Exception(ex.getMessage());
        }
    }

    /**
     * @param localFilePath 本地文件路径
     * @param sftpUploadDir 服务器目录
     * @param sftpFileNm    服务器文件名
     * @throws Exception
     * @Description: 上传文件至指定目录
     */
    public void uploadFile(String localFilePath, String sftpUploadDir, String sftpFileNm) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        OutputStream out = null;
        InputStream in = null;
        try {
            String dir = this.getSftpSaveDir(sftpUploadDir);
            channel.cd(dir);
            File file = new File(localFilePath);
            if (file.exists() && file.isFile()) {
                String fileNm = sftpFileNm;
                if (StringUtils.isBlank(fileNm)) {
                    fileNm = file.getName();
                }
                out = new BufferedOutputStream(channel.put(fileNm));
                in = new FileInputStream(file);
                byte b[] = new byte[1024];
                int n;
                while ((n = in.read(b)) != -1) {
                    out.write(b, 0, n);
                }
                out.flush();
            } else {
                throw new Exception("上传文件不存在！");
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    private String getSftpSaveDir(String dir) throws Exception {
        String path = null;
        if (StringUtils.isNotBlank(dir)) {
            path = dir;
        } else if (StringUtils.isNotBlank(this.sftpUploadDir)) {
            path = this.sftpUploadDir;
        } else if (StringUtils.isNotBlank(this.sftpDir)) {
            path = this.sftpDir;
        }
        if (StringUtils.isBlank(path)) {
            throw new Exception("sftp服务器存储目录未指定");
        }
        return path;
    }


    /**
     * @param localFilePath
     * @throws Exception
     * @Description: 上传文件至参数配置目录
     */
    public void uploadFile(String localFilePath) throws Exception {
        this.uploadFile(localFilePath, null, null);
    }

    /**
     * @param contentBytes
     * @param sftpUploadDir
     * @param sftpFileNm
     * @throws Exception
     * @Description: 上传流
     */
    public void uploadFileStream(byte[] contentBytes, String sftpUploadDir, String sftpFileNm) throws Exception {
        ByteArrayInputStream in = new ByteArrayInputStream(contentBytes);
        this.uploadFileStream(in, sftpUploadDir, sftpFileNm);
    }

    /**
     * @param in
     * @param sftpDir    sftp上传文件目录
     * @param sftpFileNm 文件名
     * @throws Exception
     * @Description: 上传文件流
     * @author sijun.yu
     * @Created 2017年7月19日上午11:29:24
     */
    public void uploadFileStream(InputStream in, String sftpDir, String sftpFileNm) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        OutputStream out = null;
        try {
            String dir = this.getSftpSaveDir(sftpDir);
            channel.cd(dir);
            channel.setFilenameEncoding("UTF-8");
            if (in != null && StringUtils.isNotBlank(sftpFileNm)) {
                out = new BufferedOutputStream(channel.put(sftpFileNm));
                byte b[] = new byte[1024];
                int n;
                while ((n = in.read(b)) != -1) {
                    out.write(b, 0, n);
                }
                out.flush();
                logger.info("upload file[" + sftpFileNm + "] to sftp");
            } else {
                throw new Exception("上传流或存储文件名不存在！");
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * @param sftpDir    可以为空，从参数配置目录下载
     * @param sftpFileNm
     * @return
     * @throws Exception
     * @Description: 下载文件存放到临时目录
     */
    public File downLoadFile(String sftpDir, String sftpFileNm) throws Exception {
        return this.downLoadFile(sftpDir, sftpFileNm, System.getProperty("java.io.tmpdir"));
    }

    /**
     * @param sftpDir    可以为空，从参数配置目录下载
     * @param sftpFileNm 文件名
     * @return
     * @throws Exception
     * @Description: 下载文件获取文件流 (注意处理后关闭流)
     */
    public ByteArrayOutputStream downLoadFileStream(String sftpDir, String sftpFileNm) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        ByteArrayOutputStream out = null;
        InputStream in = null;
        try {
            out = new ByteArrayOutputStream();
            String dir = this.getSftpSaveDir(sftpDir);
            channel.cd(dir);
            in = new BufferedInputStream(channel.get(sftpFileNm));
            byte b[] = new byte[1024];
            int n;
            while ((n = in.read(b)) != -1) {
                out.write(b, 0, n);
            }
            out.flush();
            return out;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * @param sftpDir          可以为空，从参数配置目录下载
     * @param sftpFileNm       文件名
     * @param downLoadSavePath
     * @return
     * @throws Exception
     * @Description: 获取文件
     */
    public File downLoadFile(String sftpDir, String sftpFileNm, String downLoadSavePath) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        OutputStream out = null;
        InputStream in = null;
        try {
            File f = new File(downLoadSavePath, sftpFileNm);
            out = new FileOutputStream(f);
            String dir = this.getSftpSaveDir(sftpDir);
            channel.cd(dir);
            in = new BufferedInputStream(channel.get(sftpFileNm));
            byte b[] = new byte[1024];
            int n;
            while ((n = in.read(b)) != -1) {
                out.write(b, 0, n);
            }
            out.flush();
            return f;
        } catch (Exception e) {
            this.disconnect();
            throw new Exception(e.getMessage());
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * @param sftpDir
     * @param sftpFileNm
     * @return
     * @Description: 获取文件全路径
     * @author sijun.yu
     * @Created 2017年7月19日上午11:26:24
     */
    private String getSftpFilePath(String sftpDir, String sftpFileNm) {
        if (StringUtils.endsWith("/", "\\")) {
            return sftpDir + sftpFileNm;
        } else {
            return sftpDir + "/" + sftpFileNm;
        }
    }

    /**
     * @param sftpDir    SFTP服务器下载目录
     * @param sftpFileNm 文件名
     * @throws Exception
     * @Description: 删除SFTP服务器文件
     */
    public void deleteFile(String sftpDir, String sftpFileNm) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        try {
            String dir = this.getSftpSaveDir(sftpDir);
            String path = this.getSftpFilePath(dir, sftpFileNm);
            channel.rm(path);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        }
    }

    /**
     * @param sftpDir       SFTP下载目录
     * @param localPath     本地保存目录(D:/HSBC)
     * @param fileFormat    下载文件格式(以特定字符开头,为空不做检验) ACK1PSRV3
     * @param fileEndFormat 下载文件格式(文件格式) .XML
     * @return
     * @Description: SFTP批量下载文件
     * @author chenwancheng
     * @Created 2019-10-24下午4:58:05
     */
    @SuppressWarnings("rawtypes")
    public List<String> batchDownLoadFile(String sftpDir, String localPath, String fileFormat, String fileEndFormat) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        List<String> filenames = new ArrayList<String>();
        try {
            String dir = this.getSftpSaveDir(sftpDir);
            Vector v = channel.ls(dir);
            channel.cd(dir);
            if (v.size() > 0) {
                Iterator it = v.iterator();
                while (it.hasNext()) {
                    LsEntry entry = (LsEntry) it.next();
                    String filename = entry.getFilename();
                    SftpATTRS attrs = entry.getAttrs();
                    if (!attrs.isDir()) {
                        File file = null;
                        String localFileName = localPath + File.separator + filename;
                        fileFormat = fileFormat == null ? "" : fileFormat.trim();
                        fileEndFormat = fileEndFormat == null ? "" : fileEndFormat.trim();
                        // 三种情况
                        if (fileFormat.length() > 0 && fileEndFormat.length() > 0) {
                            if (filename.startsWith(fileFormat) && filename.endsWith(fileEndFormat)) {
                                file = downLoadFile(dir, filename, localPath);
                                if (file != null) {
                                    filenames.add(localFileName);
                                }
                            }
                        } else if (fileFormat.length() > 0 && "".equals(fileEndFormat)) {
                            if (filename.startsWith(fileFormat)) {
                                file = downLoadFile(dir, filename, localPath);
                                if (file != null) {
                                    filenames.add(localFileName);
                                }
                            }
                        } else if (fileEndFormat.length() > 0 && "".equals(fileFormat)) {
                            if (filename.endsWith(fileEndFormat)) {
                                file = downLoadFile(dir, filename, localPath);
                                if (file != null) {
                                    filenames.add(localFileName);
                                }
                            }
                        } else {
                            file = downLoadFile(dir, filename, localPath);
                            if (file != null) {
                                filenames.add(localFileName);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        }
        return filenames;
    }

    /**
     * @param sftpDir    SFTP下载目录
     * @param localPath  本地保存目录(D:/JPM)
     * @param fileFormat 下载文件格式以特定字符开头:TMLCN.IRIS.MT942
     * @return
     * @throws Exception
     * @Description: 摩根-SFTP批量下载文件
     * @author chenwancheng
     * @Created 2020-1-8下午2:45:29
     */
    @SuppressWarnings("rawtypes")
    public List<String> batchDownLoadJpmFile(String sftpDir, String localPath, String fileFormat) throws Exception {
        if (session == null || !session.isConnected()) {
            this.initSftp();
        }
        List<String> filenames = new ArrayList<String>();
        try {
            String dir = this.getSftpSaveDir(sftpDir);
            Vector v = channel.ls(dir);
            channel.cd(dir);
            if (v.size() > 0) {
                Iterator it = v.iterator();
                while (it.hasNext()) {
                    LsEntry entry = (LsEntry) it.next();
                    String filename = entry.getFilename();
                    SftpATTRS attrs = entry.getAttrs();
                    if (!attrs.isDir()) {
                        File file = null;
                        String localFileName = localPath + File.separator + filename;
                        fileFormat = fileFormat == null ? "" : fileFormat.trim();
                        if (fileFormat.length() > 0) {
                            if (filename.startsWith(fileFormat)) {//文件名以****开头
                                file = downLoadFile(dir, filename, localPath);
                                if (file != null) {
                                    filenames.add(localFileName);
                                }
                            }
                        } else {
                            file = downLoadFile(dir, filename, localPath);
                            if (file != null) {
                                filenames.add(localFileName);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            this.disconnect();
            throw new Exception(e.getMessage());
        }
        return filenames;
    }

}
